package org.exhibitj.operators;

import java.util.logging.Logger;

import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.exhibitj.defect.Defect;
import org.exhibitj.operators.matchers.ControlLocator;
import org.exhibitj.operators.matchers.Matcher;

public class ShellOperator {
	private static final Logger LOG = Logger.getLogger(ShellOperator.class.getSimpleName());
	private Display display;
	private Shell shell;
	private final UIBuilder launcher;

	public ShellOperator(UIBuilder launcher) {
		this.launcher = launcher;
	}

	public void open() {
		final boolean[] started = { false };
		new Thread(new Runnable() {
			@Override
			public void run() {
				getDisplay();
				shell = new Shell(display);
				shell.setText("Default Test Shell");
				launcher.create(shell);

				LOG.info("shell is opening");
				shell.pack();
				shell.open();
				shell.forceActive();
				shell.forceFocus();
				started[0] = true;
				while (!shell.isDisposed()) {
					if (!display.readAndDispatch()) {
						display.sleep();
					}
				}
			}

			private void getDisplay() {
				while (display == null) {
					try {
						display = new Display();
					} catch (SWTError error) {
						waitForEvents();
					}
				}
			}
		}).start();
		while (!started[0]) {
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public boolean isVisible() {
		FetchingRunnable<Boolean> isVisible = new FetchingRunnable<Boolean>() {
			@Override
			public Boolean fetchValue() {
				LOG.info("Checkinf for visibility");
				return shell.isVisible();
			}
		};
		
		return (shell == null || isDisposed()) ?
				false:
				SWTDisplayThread.fetch(isVisible).booleanValue(); 
	}

	public void close() {
		if (shell == null) {
			return;
		}
		SWTDisplayThread.syncExec(new Runnable() {
			@Override
			public void run() {
				LOG.info("shell is closing");
				if (!shell.isDisposed()) {
					shell.close();
				}
				if (!display.isDisposed()) {
					display.dispose();
				}
				LOG.info("closed");
			}
		});
		try {
			while (!isDisposed()) {
				waitForEvents();
			}
		} catch (SWTException e) {
			// Silently ignore the wake-up queue and ensure it is empty.
		}
	}
	
	protected void waitForEvents() {
		display.syncExec(new Runnable() {
			@Override
			public void run() {
			}
		});
	}

	public boolean isDisposed() {
		return shell != null && shell.isDisposed();
	}

	public <T extends Control> T findWidget(final Class<T> ofType, final Matcher<T>... matchers) {
		T fetched = SWTDisplayThread.fetch(findChildMatching(ofType, matchers));

		if (fetched == null) {
			String message = "\nNo [" + ofType.getName() + "] was found\n\t\t";
			for (Matcher<T> each : matchers) {
				message += each.describe(null) + "\n\t\t";
			}
			throw new NoWidgetFoundDefect(message);
		}
		return fetched;
	}

	public <T> FetchingRunnable<T> findChildMatching(Class<T> type, Matcher<T>... matchers) {
		return new ControlLocator<T>(shell, type, matchers);
	}

	@SuppressWarnings("serial")
	public class NoWidgetFoundDefect extends Defect {

		public NoWidgetFoundDefect(String message) {
			super(message);
		}

	}

	protected String getText() {
		LOG.info("shell getText");
		return shell.getText();
	}
}
